﻿using System;
using System.Diagnostics.Contracts;

namespace Postnuclear
  {
  /// <summary>
  /// Represents an event handler with weak reference to its target.
  /// </summary>
  /// <typeparam name="TTarget">The type of the target.</typeparam>
  /// <typeparam name="TEventArgs">The type of the event arguments.</typeparam>
  internal sealed class WeakEventHandler<TTarget, TEventArgs>
    where TTarget : class
    where TEventArgs : EventArgs
    {
    #region Public

    // Constructors

    /// <summary>
    /// Initializes a new instance of the <see cref="WeakEventHandler{TTarget, TEventArgs}"/> class.
    /// </summary>
    /// <param name="handler">The handler.</param>
    /// <param name="unregisterHandler">The unregister handler which is called when target handler target is garbage collected.</param>
    public WeakEventHandler(EventHandler<TEventArgs> handler, Action<EventHandler<TEventArgs>> unregisterHandler)
      {
      Contract.Requires<ArgumentNullException>(handler != null);
      Contract.Requires<ArgumentNullException>(unregisterHandler != null);

      Target = new WeakReference<TTarget>((TTarget)handler.Target);
      TargetHandler = (TargetEventHandler)Delegate.CreateDelegate(typeof(TargetEventHandler), null, handler.Method);
      UnregisterHandler = unregisterHandler;
      Handler = Invoke;
      }

    // Fields

    /// <summary>
    /// Event handler.
    /// </summary>
    public readonly EventHandler<TEventArgs> Handler;

    #endregion

    #region Private

    // Types

    private delegate void TargetEventHandler(TTarget target, object sender, TEventArgs e);

    // Methods

    private void Invoke(object sender, TEventArgs e)
      {
      TTarget target; // Save target from GC while we are working with it

      if (Target.TryGetTarget(out target))
        {
        TargetHandler(target, sender, e);
        }
      else
        {
        UnregisterHandler(Handler);
        }
      }

    // Fields

    private readonly WeakReference<TTarget> Target;
    private readonly TargetEventHandler TargetHandler;
    private readonly Action<EventHandler<TEventArgs>> UnregisterHandler;

    #endregion
    }
  }